#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
    Part of Seagull - The Pyhon Pages engine
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    Python Page Compiler -- Writeback (should be able to write the parse tree back to it's source code)

    When the parser and this writeback module are combined, they expose an easy way for Python Page document manipulation.
"""
__author__ = 'Jonathan Slenders <jonathan at slenders dot be>'
__license__ = 'GNU LGPL'
__all__ = ('WriteBack', )

# TODO: escape characters in special tags "..."

import parser

class WriteBack(object):
	def __init__(self, parse_tree):
		assert isinstance(parse_tree, parser.PythonPage)

		self._result = [ ]

		esc=self._escape_parameter

		# Head
		self._result.append(u'{page ')
		if parse_tree.external != None:
			self._result.append(u'external="%s" ' % esc(str(parse_tree.external).lower()))
		if parse_tree.disabled != None:
			self._result.append(u'disabled="%s" ' % esc(str(parse_tree.disabled).lower()))
		if parse_tree.output_mode != None:
			self._result.append(u'output="%s" ' % esc(parse_tree.output_mode))
		if parse_tree.content_type != None:
			self._result.append(u'content-type="%s" ' % esc(parse_tree.content_type))
		if parse_tree.whitespace_compression!= None:
			self._result.append(u'compress-whitespace="%s" ' % esc(str(parse_tree.whitespace_compression).lower()))
		self._result.append(u'}\n')

		# Master
		if parse_tree.master:
			self._result.append(u'{master path="%s" ' % esc(parse_tree.master.path))

			for p in parse_tree.master.parameters:
				self._result.append(u'~%s="%s" ' % (p, esc(parse_tree.master.parameters[p])))
			self._result.append(u'}\n')

		# Parameters
		for param in parse_tree.parameters:
			self._result.append(u'{param name="%s" ' % esc(param.name))

			settings = ('type', 'classname', 'description', 'default_value', 'get_function', 'set_function')
			for i in range(0, len(settings)):
				if getattr(param, settings[i]) != None:
					self._result.append(('type', 'class', 'description', 'default-value', 'get', 'set')[i])
					self._result.append(u'="%s" ' % esc(getattr(param, settings[i])))

			self._result.append(u'}\n')

		# Meta
		for meta in parse_tree.meta:
			self._result.append(u'{meta name="%s" value="%s" }\n' % (esc(meta.name), esc(meta.value)))

		# Filters
		for filter in parse_tree.filters:
			self._result.append(u'{filter name="%s" ' % esc(filter.name))

			for p in filter.parameters:
				self._result.append(u'~%s="%s" ' % (p, esc(filter.parameters[p])))

			self._result.append(u'}\n')

		# Init block
		self._render_content(parse_tree.init_block, is_init_block=True)

		# Content blocks
		for block in parse_tree.content_blocks:
			self._result.append(u'{content name="%s" ' % esc(block.name))

			for p in block.parameters:
				self._result.append(u'~%s="%s" ' % (p, esc(block.parameters[p])))

			self._result.append (u'\\}\n' if block.skip_whitespace_after_declaration else u'}')
			self._render_content(block)

	def _render_content(self, content_block, is_init_block=False):
		for c in content_block.content:
			if isinstance(c, basestring):
				if not is_init_block:
					self._result.append(self._escape_text(c))
			else:
				if isinstance(c, parser.EmbeddedPage):
					self._render_embedded_page(c)

				elif isinstance(c, parser.Placeholder):
					self._render_placeholder(c)

				elif isinstance(c, parser.Call):
					self._render_call(c)

				elif isinstance(c, parser.PythonCode):
					self._render_python(c)

				elif isinstance(c, parser.ContentBlock.Comment):
					self._render_comment(c)

				if is_init_block:
					self._result.append('\n')

	def _render_embedded_page(self, embedded_page):
		short = len(embedded_page.content_blocks) == 0 and embedded_page.default_content_block == None
		esc = self._escape_parameter

		# 'Header'
		if short:
			self._result.append(u'{"%s" ' % esc(embedded_page.path))
		else:
			self._result.append(u'{<"%s" ' % esc(embedded_page.path))

		if embedded_page.object_varname:
			self._result.append(u'o="%s" ' % esc(embedded_page.object_varname))
		if embedded_page.params_varname:
			self._result.append(u'p="%s" ' % esc(embedded_page.params_varname))
		if embedded_page.render != None:
			self._result.append(u'render="%s" ' % esc(str(embedded_page.render).lower()))
		if embedded_page.entry != None:
			self._result.append(u'entry="%s" ' % esc(embedded_page.entry))
		for p in embedded_page.parameters:
			self._result.append(u'~%s="%s" ' % (p, esc(embedded_page.parameters[p])))

		self._result.append(u'\\}\n' if embedded_page.skip_whitespace_after_declaration else u'}')

		if not short:
			# Content blocks
			if embedded_page.default_content_block:
				self._render_content(embedded_page.default_content_block)
			for block in embedded_page.content_blocks:
				# Content block header
				self._result.append(u'{content ')
				if block.name:
					self._result.append(u'name="%s" ' % esc(block.name))

				for p in block.parameters:
					self._result.append(u'~%s="%s" ' % (p, esc(block.parameters[p])))

				self._result.append (u'\\}\n' if block.skip_whitespace_after_declaration else u'}')

				# Content
				self._render_content(block)

			# 'Footer'
			self._result.append(u'{> ')
			self._result.append(u'\\}\n' if embedded_page.skip_whitespace_after_end_declaration else u'}')

	def _render_placeholder(self, placeholder):
		self._result.append(u'{placeholder name="%s" ' % self._escape_text(placeholder.name))
		self._result.append(u'\\}\n' if placeholder.skip_whitespace_after_declaration else u'}')

	def _render_call(self, call):
		self._result.append(u'{call entry="%s" ' % self._escape_text(call.entry))
		self._result.append(u'\\}\n' if call.skip_whitespace_after_declaration else u'}')

	def _render_python(self, python):
		self._result.append(u'{=' if python.print_result_escaped else u'{%')

		for c in python.code:
			if isinstance(c, parser.PythonCode.Comment):
				for line in c.content.split('\n'):
					self._result.append(u'#%s\n' % line)
			elif isinstance(c, parser.PythonCode.Code): # TODO: what if %> appears in code
				self._result.append(c.content)
			elif isinstance(c, parser.PythonCode.String):
				type = c.quote_type
	
				if type in ('triple-double-quoted', 'triple-double-quoted-raw'):
					self._result.append(u'"""%s"""' % c.content)

				elif type in ('triple-single-quoted', 'triple-single-quoted-raw'):
					self._result.append(u"'''%s'''" % c.content)

				elif type in ('double-quoted', 'double-quoted-raw'):
					self._result.append(u'"%s"' % c.content)

				elif type in ('single-quoted', 'single-quoted-raw'):
					self._result.append(u"'%s'" % c.content)
				else:
					raise Exception('Unknown string type')

		if python.skip_whitespace_after_declaration:
			self._result.append(u'=\\}\n' if python.print_result_escaped else u'%\\}\n')
		else:
			self._result.append(u'=}' if python.print_result_escaped else u'%}')

	def _render_comment(self, comment):
		c = comment.content
		while c.find(u'-->') >= 0:
			c = c.replace(u'-->', u'')

		self._result.append(u'<!--%s-->' % c)

	def _escape_parameter(self, parameter_value):
		"""
		Escape text in {tag  key="(here)" }
		Inserts a backslash before double-quote/backslash
		"""
		return parameter_value.replace('\\', '\\\\').replace('"', '\\"')

	def _escape_text(self, text):
		"""
		Escape special characters in text (which are meant to be content)
			{    -->   {$  {  $}
			}    -->   {$  }  $}
		"""
		to_escape = (u'{', u'}')

		# find position of special characters
		pos = -1
		char = ''
		for c in to_escape:
			pos2 = text.find(c)
			if pos2 >= 0:
				pos = (min(pos, pos2) if pos >= 0 else pos2)
				char = c
		
		# Return escaped self._result
		if pos >= 0:
			return u'%s{$%s$}%s' % (text[:pos], char, self._escape_text(text[pos+2:]))
		else:
			return text

	def get_result(self):
		return u''.join(self._result)
